package com.huijie.app.u.shiro.impl;


import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import com.huijie.app.configuration.RedisConfiguration;
import com.huijie.app.u.constant.UserTypeEnum;
import com.huijie.app.u.entity.Token;
import com.huijie.app.u.entity.User;
import com.huijie.app.u.service.IUserRoleService;
import com.huijie.app.u.service.IUserService;
import com.huijie.app.util.RedisUtil;
import com.huijie.app.util.TokenUtil;
import com.huijie.core.util.ThreadPool;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.shiro.authc.AuthenticationException;
import org.jsets.shiro.config.MessageConfig;
import org.jsets.shiro.exception.JwtException;
import org.jsets.shiro.service.ShiroStatelessAccountProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
* @ClassName: StatelessAccountProviderImpl
* @Description: 无状态账号信息提供者实现
* @author Alex
* @date 2018年7月3日 下午2:44:03
*/
@Service
public class StatelessAccountProviderImpl implements ShiroStatelessAccountProvider {

    private static final Log log = LogFactory.getLog(StatelessAccountProviderImpl.class);
	
	@Autowired
	private IUserRoleService userRoleService;
	@Autowired
	private IUserService userService;

	@Override
	public boolean checkAccount(String appId) throws AuthenticationException {
		// 此方法不适用多用户类型
		//所有账号提供方均不存在
		throw new AuthenticationException("账号不存在");
	}

	@Override
	public boolean checkAccountByUserType(String appId, Integer userType) throws AuthenticationException {
		if(userType== UserTypeEnum.SYSTEM.getCode()){
			User user = userService.getUserByloginName(appId);
			//后台账号存在
			if(null != user) {
				return true;
			}
		}
		//所有账号提供方均不存在
		throw new AuthenticationException("账号不存在或不合法");
	}

	@Override
	public String loadAppKey(String appId) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Set<String> loadRoles(String appId) {
		return Sets.newHashSet(userRoleService.listUserRoles(appId));
	}

	@Override
	public Set<String> loadPermissions(String appId) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Set<String> loadRoles(Map<String,Object> token) {
		Integer userType = (Integer) token.get("userType");
		if(UserTypeEnum.SYSTEM.getCode()==userType){
			//系统账号
			return Sets.newHashSet(userRoleService.listUserRoles((String) token.get("appId")));
		}
		return Sets.newHashSet();
	}

	@Override
	public Set<String> loadPermissions(Map<String,Object> token) {
		return null;
	}

	/**
	 * @param tokenId
	 * @param key
	 * @return void
	 * @description: 同步缓存jwtToken
	 * @author zyc
	 * @date 2018/12/20 17:38
	 */
	public void cacheJwtToken(String tokenId, String key) {
		if(StringUtils.isNotEmpty(tokenId)) {
			Token token = TokenUtil.getToken(tokenId);
			if(token!=null){
				Date expiration = new Date(System.currentTimeMillis() + TokenUtil.getJwtPeriod());
				//更新最新时间到数据库
				Token tokenUpdate = new Token();
				tokenUpdate.setTokenId(tokenId);
				tokenUpdate.setExpiration(expiration);
				token.setExpiration(expiration);
				TokenUtil.modifySync(tokenUpdate);
				//更新最新时间，缓存到redis(tokenId+分隔符+appId+固定值->key)
				RedisUtil.setex(key, JSON.toJSONString(token), (TokenUtil.getJwtPeriod().intValue()/1000));
			}
		}
	}

	/**
	 * @param tokenId
	 * @param key
	 * @return void
	 * @description: 异步缓存jwtToken
	 * @author zyc
	 * @date 2018/12/20 17:38
	 */
	public void cacheJwtTokenAsync(String tokenId, String key) {
		ThreadPool.getPool().execute(new CacheTokenRunner(this,tokenId,key));
	}

	class CacheTokenRunner implements Runnable{
		private Log log = LogFactory.getLog(CacheTokenRunner.class);

		public StatelessAccountProviderImpl statelessAccountProvider;
		private String tokenId;
		private String key;

		public CacheTokenRunner(StatelessAccountProviderImpl statelessAccountProvider, String tokenId, String key) {
			this.statelessAccountProvider = statelessAccountProvider;
			this.tokenId = tokenId;
			this.key = key;
		}

		@Override
		public void run() {
			long start = System.currentTimeMillis();
			log.debug("更新令牌信息线程开始,token:"+tokenId);
			statelessAccountProvider.cacheJwtToken(tokenId,key);
			log.debug("更新令牌信息线程结束,用时" + (System.currentTimeMillis() - start) + "毫秒");
		}
	}

	@Override
	public void checkTokenStatus(String tokenId, String appId) {

		HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

		//验证超时/心跳
		String key = new StringBuffer().append(tokenId).append(RedisConfiguration.CUT).append(appId).append(RedisConfiguration.TOKEN).toString();

		//从redis获取token(key->tokenId+分隔符+appId+固定值)
		if(RedisUtil.exists(key)) {
			//异步缓存jwtToken
			this.cacheJwtTokenAsync(tokenId,key);
		}
		//数据库获取token
		Token token = TokenUtil.getToken(tokenId);
		if(null == token) {
			throw new JwtException(MessageConfig.MSG_BURNED_TOKEN);
		}
		// 检查账号
		boolean checkAccount = this.checkAccountByUserType(token.getAppId(), token.getUserType());
		if(!checkAccount){
			throw new AuthenticationException(MessageConfig.MSG_ACCOUNT_EXCEPTION);
		}
		request.setAttribute(TokenUtil.TOKEN_ATTRITUBE,token);
		long expirationL = token.getExpiration().getTime();
		long currenTimeMillisL = System.currentTimeMillis();
		if(currenTimeMillisL > expirationL) {
			TokenUtil.delete(token);
			throw new JwtException(MessageConfig.MSG_BURNED_TOKEN);
		} else {
			//异步缓存jwtToken
			this.cacheJwtTokenAsync(tokenId,key);
		}
	}

	@Override
	public Map<String, Object> getTokenMap(String tokenId) {
		Map<String, Object> tokenMap;
		Token token = TokenUtil.getToken(tokenId);
		try {
			tokenMap = PropertyUtils.describe(token);
		} catch (Exception e) {
			tokenMap = new HashMap<>(8);
			e.printStackTrace();
		}
		return tokenMap;
	}

}